package codec

import (
	"gitee.com/yanwc/ebike-xiaoan-device/internal/field"
	"gitee.com/yanwc/ebike-xiaoan-device/internal/frame"
	"gitee.com/yanwc/net/utils"
	gerrors "github.com/panjf2000/gnet/pkg/errors"
)

var magic frame.Magic = 0xAA55 // 标识数据包的开始
// 头字段
type (
	ContentLength    uint16
	ContentLengthRaw [2]byte // 内容长度
)

func (cl ContentLengthRaw) Type() ContentLength {
	return utils.BytesToNumber[ContentLength](cl[:])
}

func (cl ContentLength) Raw() (out ContentLengthRaw) {
	copy(out[:], utils.NumberToBytesWithBigEndian(cl))
	return
}

// 帧头定义
type Header struct {
	CmdWord       field.CommandWord  // 命令字
	SN            field.SerialNumber // 序号
	ContentLength ContentLength      // 长度
}

func (h *Header) Len() int {
	return len(field.CommandWordRaw{}) + len(field.SerialNumberRaw{}) + len(ContentLengthRaw{})
}

// 帧协议
type Frame struct {
	Header  *Header // 头部信息
	rawData []byte  // 原始数据帧
}

var _ frame.IFrame = (*Frame)(nil)

func NewEmptyFrame() *Frame {
	return &Frame{
		Header: &Header{},
	}
}

func NewFrame(cmdWord field.CommandWord, sn field.SerialNumber) *Frame {
	return &Frame{
		Header: &Header{
			CmdWord: cmdWord,
			SN:      sn,
		},
	}
}

func (fc *Frame) WrapPacket(data []byte) (out []byte) {
	sn := fc.Header.SN.Raw()
	cmdWord := fc.Header.CmdWord.Raw()
	out = utils.WrapPacket(
		utils.NumberToBytesWithBigEndian(magic), // 帧标记
		cmdWord[:],                              // 命令字
		sn[:],                                   // 命令序列号
		utils.NumberToBytesWithBigEndian(uint16(len(data))), // 数据长度
		data, // 数据内容
	)
	return
}

func (fc *Frame) UnWrapPacket(data []byte) (err error) {
	if len(data) < 4 {
		return gerrors.ErrIncompletePacket
	}

	var cmdWord field.CommandWordRaw
	var sn field.SerialNumberRaw
	var length ContentLengthRaw
	utils.LoadPacket(data, cmdWord[:], sn[:], length[:])
	fc.Header.CmdWord = cmdWord.Type()
	fc.Header.SN = sn.Type()
	fc.Header.ContentLength = length.Type()
	fc.rawData = data
	return
}

func (fc *Frame) RawData() []byte {
	return fc.rawData
}

func (fc *Frame) Content() []byte {
	startIndex := fc.Header.Len()
	endIndex := startIndex + int(fc.Header.ContentLength)
	return fc.rawData[startIndex:endIndex]
}

func (fc *Frame) GetSN() field.SerialNumber {
	return fc.Header.SN
}

func (fc *Frame) GetCmdWord() field.CommandWord {
	return fc.Header.CmdWord
}

func (fc *Frame) CRCCheck() bool {
	return true
}

func WrapPacket[T ~[]byte](f *Frame, fields ...T) (out []byte) {
	var tmp []byte
	for _, f := range fields {
		tmp = append(tmp, f...)
	}

	return f.WrapPacket(tmp)
}
